/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "CoapResource.h"

#include <bits/alltypes.h>
#include <cstdio>

#include "coap3/coap_resource.h"
#include "CoapExchange.h"
#include "third_party_bounds_checking_function/include/securec.h"

static std::unordered_map<std::string, CoapResource::ResourceHandler> g_resourceMap;

static const int FIRST = 0;
static const int SECOND = 1;
static const int THIRD = 2;
static const int COUNT_ONE = 1;
static const int COUNT_TWO = 2;
static const int COUNT_THREE = 3;

static const int IDSIZE = 16;
static const int DEFAULT_BUFF_SIZE = 1024;
static const int ATTRIBUTE_BUFF_SIZE = 10240;  // 10KB

coap_resource_t *CoapResource::getResource(std::string resourceId)
{
    coap_resource_t *resource = nullptr;
    auto iter = g_resourceMap.find(resourceId);
    if (iter != g_resourceMap.end()) {
        resource = g_resourceMap[resourceId].resource;
    }
    return resource;
}

napi_threadsafe_function CoapResource::getPostHandler(std::string resourceId)
{
    napi_threadsafe_function fn = nullptr;
    auto iter = g_resourceMap.find(resourceId);
    if (iter != g_resourceMap.end()) {
        fn = g_resourceMap[resourceId].postHandler;
    }
    return fn;
}

napi_threadsafe_function CoapResource::getGetHandler(std::string resourceId)
{
    napi_threadsafe_function fn = nullptr;
    auto iter = g_resourceMap.find(resourceId);
    if (iter != g_resourceMap.end()) {
        fn = g_resourceMap[resourceId].getHandler;
    }
    return fn;
}

static void CallJs(napi_env env, napi_value jsCb, void *context, void *data)
{
    napi_value undefined;
    napi_value argv;
    napi_value ret;
    napi_create_string_utf8(env, (char *)data, NAPI_AUTO_LENGTH, &argv);
    napi_call_function(env, undefined, jsCb, 1, &argv, &ret);
}

static void hnd_get_index(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request,
                          const coap_string_t *query, coap_pdu_t *response)
{
    char responseId[IDSIZE];
    (void)sprintf_s(responseId, sizeof(responseId), "%#lx", response);
    char resourceId[IDSIZE];
    (void)sprintf_s(resourceId, sizeof(resourceId), "%#lx", resource);
    // 通过responseID添加对应的response节点
    CoapExchange::ResponseTool tmp = CoapExchange::SetResponseTool(resource, session, request, query, response);
    CoapExchange::AddResponse(responseId, tmp);
    // 通过resourceId找到回调函数
    napi_threadsafe_function tsfn = CoapResource::getGetHandler(resourceId);
    napi_call_threadsafe_function(tsfn, responseId, napi_tsfn_blocking);
    CoapExchange::Wait(responseId);
    CoapExchange::Destroy(responseId);
}

static void hnd_post_index(coap_resource_t *resource, coap_session_t *session, const coap_pdu_t *request,
                           const coap_string_t *query, coap_pdu_t *response)
{
    char responseId[IDSIZE];
    (void)sprintf_s(responseId, sizeof(responseId), "%#lx", response);
    char resourceId[IDSIZE];
    (void)sprintf_s(resourceId, sizeof(resourceId), "%#lx", resource);
    // 通过responseID添加对应的response节点
    CoapExchange::ResponseTool tmp = CoapExchange::SetResponseTool(resource, session, request, query, response);
    CoapExchange::AddResponse(responseId, tmp);
    // 通过responseId找到回调函数
    napi_threadsafe_function tsfn = CoapResource::getPostHandler(resourceId);
    napi_call_threadsafe_function(tsfn, responseId, napi_tsfn_blocking);
    CoapExchange::Wait(responseId);
    CoapExchange::Destroy(responseId);
}

std::string CoapResource::setResourceHandler(coap_resource_t *resource, std::string uri,
                                             napi_threadsafe_function getHandler,
                                             napi_threadsafe_function postHandler)
{
    char resourceId[IDSIZE];
    (void)sprintf_s(resourceId, sizeof(resourceId), "%#lx", resource);
    auto iter = g_resourceMap.find(resourceId);
    if (iter == g_resourceMap.end()) {
        CoapResource::ResourceHandler tmp = {uri, resource, getHandler, postHandler};
        g_resourceMap.insert(std::pair<std::string, CoapResource::ResourceHandler>(resourceId, tmp));
    }
    // 允许重新设置回调函数
    if (getHandler != nullptr) {
        g_resourceMap[resourceId].getHandler = getHandler;
    }
    if (postHandler != nullptr) {
        g_resourceMap[resourceId].postHandler = postHandler;
    }
    std::string ret(resourceId);
    return ret;
}

bool CoapResource::ParamFilter(napi_env env, napi_callback_info info,
                               napi_threadsafe_function *tsfn,
                               coap_resource_t **resource)
{
    size_t argc = 2;
    napi_value args[2] = {nullptr};
    napi_status status;

    status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc != COUNT_TWO) {
        napi_throw_type_error(env, nullptr, "Expected 2 arguments for RegisterHandler.");
        return false;
    }

    char resourceId[DEFAULT_BUFF_SIZE] = {0};
    size_t resourceIdLen = sizeof(resourceId) - 1;
    status = napi_get_value_string_utf8(env, args[FIRST], resourceId, resourceIdLen, &resourceIdLen);
    if (status != napi_ok) {
        napi_throw_type_error(env, nullptr, "Failed to convert first argument to UTF-8 string (resource ID).");
        return false;
    }

    *resource = CoapResource::getResource(resourceId);
    if (!*resource) {
        napi_throw_error(env, nullptr, "Resource not found.");
        return false;
    }

    napi_value workName;
    napi_create_string_utf8(env, "threadSafeFunc", NAPI_AUTO_LENGTH, &workName);
    napi_value jsCb = args[SECOND];

    // 创建线程安全post回调函数
    napi_create_threadsafe_function(env, jsCb, nullptr, workName, 0, 1, nullptr, nullptr, nullptr, CallJs, tsfn);
    napi_acquire_threadsafe_function(*tsfn);
    return true;
}

napi_value CoapResource::RegisterPostHandler(napi_env env, napi_callback_info info)
{
    napi_threadsafe_function tsfn;
    coap_resource_t *resource;
    CoapResource::ParamFilter(env, info, &tsfn, &resource);
    setResourceHandler(resource, "", nullptr, tsfn);

    // 注册POST请求的处理程序
    coap_register_request_handler(resource, COAP_REQUEST_POST, hnd_post_index);
    return nullptr;
}

napi_value CoapResource::RegisterGetHandler(napi_env env, napi_callback_info info)
{
    napi_threadsafe_function tsfn;
    coap_resource_t *resource;
    if (!CoapResource::ParamFilter(env, info, &tsfn, &resource)) {
        return nullptr;
    }
    setResourceHandler(resource, "", tsfn, nullptr);

    // 注册GET请求的处理程序
    coap_register_request_handler(resource, COAP_REQUEST_GET, hnd_get_index);
    return nullptr;
}

napi_value CoapResource::Destroy(napi_env env, napi_callback_info info)
{
    g_resourceMap.clear();
    return nullptr;
}

napi_value CoapResource::CreateResource(napi_env env, napi_callback_info info)
{
    size_t argc = 1;
    napi_value args[1] = {nullptr};
    napi_valuetype valuetype;

    napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc != COUNT_ONE) {
        napi_throw_type_error(env, nullptr, "Expected 1 arguments for CreateResource.");
        return nullptr;
    }

    char resourceUri[DEFAULT_BUFF_SIZE] = {0};
    size_t charLen = sizeof(resourceUri) - 1;
    status = napi_get_value_string_utf8(env, args[FIRST], resourceUri, charLen, &charLen);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    std::string resourceUriStr(resourceUri);
    coap_resource_t *resource =
        coap_resource_init(coap_make_str_const(resourceUriStr.c_str()), COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT);
    if (!resource) {
        napi_throw_error(env, nullptr, "Failed to run coap_resource_init.");
        return nullptr;
    }

    std::string resourceId = setResourceHandler(resource, resourceUriStr, nullptr, nullptr);
    napi_value ret;
    napi_create_string_utf8(env, resourceId.c_str(), NAPI_AUTO_LENGTH, &ret);
    return ret;
}

napi_value CoapResource::JsConstructor(napi_env env, napi_callback_info info)
{
    napi_value targetObj = nullptr;
    void *data = nullptr;
    size_t argc = 0;
    napi_value args[1] = {nullptr};
    napi_status status = napi_get_cb_info(env, info, &argc, args, &targetObj, &data);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to retrieve callback info.");
        return nullptr;
    }

    CoapResource *classBind = new CoapResource();
    napi_wrap(
        env, nullptr, classBind,
        [](napi_env env, void *data, void *hint) {
            CoapResource *bind = (CoapResource *)data;
            delete bind;
            bind = nullptr;
        },
        nullptr, nullptr);
    return targetObj;
}

napi_value CoapResource::AddAttributes(napi_env env, napi_callback_info info)
{
    size_t argc = 3;
    napi_value args[3] = {nullptr};

    napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    if (status != napi_ok || argc != COUNT_THREE) {
        napi_throw_type_error(env, nullptr, "Expected 3arguments for AddAttributes.");
        return nullptr;
    }

    char resourceId[DEFAULT_BUFF_SIZE] = {0};
    size_t resourceIdLen = sizeof(resourceId) - 1;

    status = napi_get_value_string_utf8(env, args[FIRST], resourceId, resourceIdLen, &resourceIdLen);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    std::string resourceIdStr(resourceId);
    coap_resource_t *resource = CoapResource::getResource(resourceIdStr);
    if (!resource) {
        napi_throw_error(env, nullptr, "Resource not found.");
        return nullptr;
    }

    char resourceKey[DEFAULT_BUFF_SIZE] = {0};
    size_t resourceKeyLen = sizeof(resourceKey) - 1;
    status = napi_get_value_string_utf8(env, args[SECOND], resourceKey, resourceKeyLen, &resourceKeyLen);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    char resourceValue[ATTRIBUTE_BUFF_SIZE] = {0};
    size_t resourceValueLen = sizeof(resourceValue) - 1;
    status = napi_get_value_string_utf8(env, args[THIRD], resourceValue, resourceValueLen, &resourceValueLen);
    if (status != napi_ok) {
        napi_throw_error(env, nullptr, "Failed to convert argument to UTF-8 string.");
        return nullptr;
    }

    coap_add_attr(resource, coap_make_str_const(resourceKey), coap_make_str_const(resourceValue), 0);
    return nullptr;
}
